package heap

import (
	"go_gvm_mini/classfile"
	"strings"
)

type Klass struct {
	// 修饰符
	accessFlags uint16
	// 类名称
	name string
	// 源文件名称
	sourceFile string
	// 父类class名称
	superClassName string
	// 接口名称
	interfaceNames []string
	// 常量池
	constantPool *ConstantPool

	fields []*Field

	methods []*Method

	// 类加载器
	classloader ClassLoader

	superClass *Klass
	interfaces []*Klass

	// 堆空间的字节码对象(反射使用...)
	jClass *Object

	// 成员变量或者叫实例变量 的槽数
	instanceSlotCount uint
	// 静态变量 的槽数
	staticSlotCount uint
	// 静态变量
	staticVars  Slots
	initStarted bool
}

func (klass *Klass) IsArray() bool {
	return klass.name[0] == '['
}

func (k *Klass) IsPublic() bool {
	return 0 != k.accessFlags&ACC_PUBLIC
}
func (k *Klass) IsFinal() bool {
	return 0 != k.accessFlags&ACC_FINAL
}
func (k *Klass) IsSuper() bool {
	return 0 != k.accessFlags&ACC_SUPER
}
func (k *Klass) IsInterface() bool {
	return 0 != k.accessFlags&ACC_INTERFACE
}
func (k *Klass) IsAbstract() bool {
	return 0 != k.accessFlags&ACC_ABSTRACT
}
func (k *Klass) IsSynthetic() bool {
	return 0 != k.accessFlags&ACC_SYNTHETIC
}
func (k *Klass) IsAnnotation() bool {
	return 0 != k.accessFlags&ACC_ANNOTATION
}
func (k *Klass) IsEnum() bool {
	return 0 != k.accessFlags&ACC_ENUM
}

func (k *Klass) isAccessibleTo(other *Klass) bool {
	return k.IsPublic() || k.getPackageName() == other.getPackageName()
}

func (k *Klass) ComponentClass() *Klass {
	componentClassName := getComponentClassName(k.name)
	return k.classloader.LoadClass(componentClassName)
}

func getComponentClassName(name string) string {
	if name[0] == '[' {
		componentTypeDescriptor := name[1:]
		return toClassName(componentTypeDescriptor)
	}
	panic("Not Array：" + name)
}

func toClassName(descriptor string) string {
	if descriptor[0] == '[' {
		return descriptor
	}
	if descriptor[0] == 'L' {
		return descriptor[1 : len(descriptor)-1]
	}
	for className, d := range primitiveTypes {
		if d == descriptor {
			return className
		}
	}
	panic("Invalid descriptor: " + descriptor)
}

func (k *Klass) GetFields(publicOnly bool) []*Field {
	if publicOnly {
		publicFields := make([]*Field, 0, len(k.fields))
		for _, field := range k.fields {
			if field.IsPublic() {
				publicFields = append(publicFields, field)
			}
		}
		return publicFields
	} else {
		return k.fields
	}
}

func (self *Klass) GetConstructor(descriptor string) *Method {
	return self.GetInstanceMethod("<init>", descriptor)
}

func (k *Klass) isJlObject() bool {
	return k.name == "java/lang/Object"
}
func (k *Klass) isJlCloneable() bool {
	return k.name == "java/lang/Cloneable"
}
func (k *Klass) isJioSerializable() bool {
	return k.name == "java/io/Serializable"
}

func (self *Klass) getField(name string, descriptor string, isStatic bool) *Field {
	for c := self; c != nil; c = c.superClass {
		for _, field := range c.fields {
			if field.IsStatic() == isStatic &&
				field.name == name && field.descriptor == descriptor {
				return field
			}
		}
	}
	return nil
}

// 获取包名称
func (self Klass) getPackageName() string {
	if i := strings.LastIndex(self.name, "/"); i >= 0 {
		return self.name[:i]
	}
	return ""
}

// 创建 Klass 对应的堆内存中字节码对象
func (klass *Klass) NewObject() *Object {
	return newObject(klass)
}

func (k *Klass) SetRefVar(fieldName, fieldDescriptor string, ref *Object) {
	field := k.getField(fieldName, fieldDescriptor, true)
	k.staticVars.SetRef(field.slotId, ref)
}

func (klass *Klass) StartInit() {
	klass.initStarted = true
}

func (k *Klass) getStaticMethod(name string, descriptor string) *Method {
	for _, method := range k.methods {
		if method.IsStatic() && method.name == name && method.descriptor == descriptor {
			return method
		}
	}
	return nil
}

func (k *Klass) GetClinitMethod() *Method {
	return k.getStaticMethod("<clinit>", "()V")
}

func (klass *Klass) SuperClass() *Klass {
	return klass.superClass
}

func (klass *Klass) InitStarted() bool {

	return klass.initStarted

}

func (klass *Klass) GetMainMethod() *Method {
	return klass.getStaticMethod("main", "([Ljava/lang/String;)V")
}

func (k *Klass) ArrayClass() *Klass {
	arrayClassName := getArrayClassName(k.name)
	return k.classloader.LoadClass(arrayClassName)
}

func (k *Klass) NewArray(count uint) *Object {
	if !k.IsArray() {
		panic("Not Array Class: " + k.name)
	}
	switch k.name {
	case "[Z":
		return &Object{k, make([]int8, count), nil}
	case "[B":
		return &Object{k, make([]int8, count), nil}
	case "[C":
		return &Object{k, make([]uint16, count), nil}
	case "[S":
		return &Object{k, make([]int16, count), nil}
	case "[I":
		return &Object{k, make([]int32, count), nil}
	case "[J":
		return &Object{k, make([]int64, count), nil}
	case "[F":
		return &Object{k, make([]float32, count), nil}
	case "[D":
		return &Object{k, make([]float64, count), nil}
	default:
		return &Object{k, make([]*Object, count), nil}
	}
}

func (klass *Klass) Name() string {
	return klass.name
}

func (klass *Klass) GetClassLoader() ClassLoader {
	return klass.classloader
}

func (klass *Klass) JClass() *Object {
	return klass.jClass
}

func (klass *Klass) ConstantPool() *ConstantPool {
	return klass.constantPool
}

func (klass *Klass) GetStaticMethod(name, descriptor string) *Method {

	return klass.getMethod(name, descriptor, true)

}

func (k *Klass) getMethod(name, descriptor string, isStatic bool) *Method {
	for c := k; c != nil; c = c.superClass {
		for _, method := range c.methods {
			if method.IsStatic() == isStatic &&
				method.name == name &&
				method.descriptor == descriptor {

				return method
			}
		}
	}
	return nil
}

func (klass *Klass) AccessFlags() uint16 {
	return klass.accessFlags
}

func (k *Klass) GetInstanceMethod(name, descriptor string) *Method {
	return k.getMethod(name, descriptor, false)
}

func (klass *Klass) SourceFile() string {
	return klass.sourceFile
}

func (klass *Klass) IsPrimitive() bool {
	_, ok := primitiveTypes[klass.name]
	return ok
}

func (klass *Klass) IsSuperClassOf(class *Klass) bool {
	return class.IsSubClassOf(klass)
}

func (klass *Klass) GetPackageName() string {
	return klass.getPackageName()
}

func (klass *Klass) StaticVars() Slots {
	return klass.staticVars
}

func newObject(k *Klass) *Object {
	return &Object{
		class: k,
		data:  newSlots(k.instanceSlotCount),
	}
}

func newSlots(maxLocals uint) Slots {
	if maxLocals > 0 {
		return make([]Slot, maxLocals)
	}
	return nil
}

func newKlass(cf *classfile.ClassFile) *Klass {
	class := &Klass{}
	class.accessFlags = cf.AccessFlags()
	class.name = cf.ClassName()
	class.sourceFile = cf.GetSourceFile(cf)
	class.superClassName = cf.SuperClassName()
	class.interfaceNames = cf.InterfaceNames()
	class.constantPool = NewConstantPool(class, cf.GetConstantPool())
	class.fields = NewFields(class, cf.Fields())
	class.methods = NewMethods(class, cf.Methods())
	return class
}
func NewByteArray(loader *ClassLoader, bytes []int8) *Object {
	return &Object{loader.LoadClass("[B"), bytes, nil}
}
func LookupMethodInClass(class *Klass, name string, descriptor string) *Method {
	for c := class; c != nil; c = c.superClass {
		for _, method := range c.methods {
			if method.name == name && method.descriptor == descriptor {
				return method
			}
		}
	}
	return nil
}
